// tslint:disable
{{>licenseInfo}}
import { Observable, of } from 'rxjs';
import { ajax, AjaxRequest, AjaxResponse } from 'rxjs/ajax';
import { map, concatMap } from 'rxjs/operators';

export const BASE_PATH = '{{{basePath}}}'.replace(/\/+$/, '');

export interface ConfigurationParameters {
    basePath?: string; // override base path
    middleware?: Middleware[]; // middleware to apply before/after rxjs requests
    username?: string; // parameter for basic security
    password?: string; // parameter for basic security
    apiKey?: string | ((name: string) => string); // parameter for apiKey security
    accessToken?: string | ((name?: string, scopes?: string[]) => string); // parameter for oauth2 security
}

export class Configuration {
    constructor(private configuration: ConfigurationParameters = {}) {}

    get basePath(): string {
        return this.configuration.basePath || BASE_PATH;
    }

    get middleware(): Middleware[] {
        return this.configuration.middleware || [];
    }

    get username(): string | undefined {
        return this.configuration.username;
    }

    get password(): string | undefined {
        return this.configuration.password;
    }

    get apiKey(): ((name: string) => string) | undefined {
        const apiKey = this.configuration.apiKey;
        if (apiKey) {
            return typeof apiKey === 'function' ? apiKey : () => apiKey;
        }
        return undefined;
    }

    get accessToken(): ((name: string, scopes?: string[]) => string) | undefined {
        const accessToken = this.configuration.accessToken;
        if (accessToken) {
            return typeof accessToken === 'function' ? accessToken : () => accessToken;
        }
        return undefined;
    }
}

/**
 * This is the base class for all generated API classes.
 */
export class BaseAPI {
    private middleware: Middleware[] = [];

    constructor(protected configuration = new Configuration()) {
        this.middleware = configuration.middleware;
    }

    withMiddleware = <T extends BaseAPI>(middlewares: Middleware[]) => {
        const next = this.clone<T>();
        next.middleware = next.middleware.concat(middlewares);
        return next;
    };

    withPreMiddleware = <T extends BaseAPI>(preMiddlewares: Array<Middleware['pre']>) =>
        this.withMiddleware<T>(preMiddlewares.map((pre) => ({ pre })));

    withPostMiddleware = <T extends BaseAPI>(postMiddlewares: Array<Middleware['post']>) =>
        this.withMiddleware<T>(postMiddlewares.map((post) => ({ post })));

    protected request = <T>(requestOpts: RequestOpts): Observable<T> =>
        this.rxjsRequest(this.createRequestArgs(requestOpts)).pipe(
            map((res) => {
                if (res.status >= 200 && res.status < 300) {
                    return res.response as T;
                }
                throw res;
            })
        );

    private createRequestArgs = (requestOpts: RequestOpts): RequestArgs => {
        let url = this.configuration.basePath + requestOpts.path;
        if (requestOpts.query !== undefined && Object.keys(requestOpts.query).length !== 0) {
            // only add the queryString to the URL if there are query parameters.
            // this is done to avoid urls ending with a '?' character which buggy webservers
            // do not handle correctly sometimes.
            url += '?' + queryString(requestOpts.query);
        }

        return {
            url,
            method: requestOpts.method,
            headers: requestOpts.headers,
            body: requestOpts.body instanceof FormData ? requestOpts.body : JSON.stringify(requestOpts.body),
            responseType: requestOpts.responseType || 'json',
        };
    }

    private rxjsRequest = (params: RequestArgs): Observable<AjaxResponse> =>
        of(params).pipe(
            map((request) => {
                this.middleware.filter((item) => item.pre).forEach((mw) => (request = mw.pre!(request)));
                return request;
            }),
            concatMap((args) =>
                ajax(args).pipe(
                    map((response) => {
                        this.middleware.filter((item) => item.post).forEach((mw) => (response = mw.post!(response)));
                        return response;
                    })
                )
            )
        );

    /**
     * Create a shallow clone of `this` by constructing a new instance
     * and then shallow cloning data members.
     */
    private clone = <T extends BaseAPI>(): T => 
        Object.assign(Object.create(Object.getPrototypeOf(this)), this);
}

// export for not being a breaking change
export class RequiredError extends Error {
    name: 'RequiredError' = 'RequiredError';
}

export const COLLECTION_FORMATS = {
    csv: ',',
    ssv: ' ',
    tsv: '\t',
    pipes: '|',
};

export type Json = any;
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS' | 'HEAD';
export type HttpHeaders = { [key: string]: string };
export type HttpQuery = Partial<{ [key: string]: string | number | null | boolean | Array<string | number | null | boolean> }>; // partial is needed for strict mode
export type HttpBody = Json | FormData;
export type ModelPropertyNaming = 'camelCase' | 'snake_case' | 'PascalCase' | 'original';

export interface RequestOpts {
    path: string;
    method: HttpMethod;
    headers?: HttpHeaders;
    query?: HttpQuery;
    body?: HttpBody;
    responseType?: 'json' | 'blob' | 'arraybuffer' | 'text';
}

export const encodeURI = (value: any) => encodeURIComponent(String(value));

const queryString = (params: HttpQuery): string => Object.keys(params)
    .map((key) => {
        const value = params[key];
        return (value instanceof Array)
            ? value.map((val) => `${encodeURI(key)}=${encodeURI(val)}`).join('&')
            : `${encodeURI(key)}=${encodeURI(value)}`;
    })
    .join('&');

// alias fallback for not being a breaking change
export const querystring = queryString;

export const throwIfRequired = (params: {[key: string]: any}, key: string, nickname: string) => {
    if (!params || params[key] === null || params[key] === undefined) {
        throw new RequiredError(`Required parameter ${key} was null or undefined when calling ${nickname}.`);
    }
};

// alias for easier importing
export interface RequestArgs extends AjaxRequest {}
export interface ResponseArgs extends AjaxResponse {}

export interface Middleware {
    pre?(request: RequestArgs): RequestArgs;
    post?(response: ResponseArgs): ResponseArgs;
}
